package top.malaoshi.webmvc;

import org.springframework.util.ClassUtils;
import top.malaoshi.easy.logging.Log;
import top.malaoshi.easy.logging.LogFactory;
import top.malaoshi.sprout.ioc.DefaultSingletonBeanRegistry;
import top.malaoshi.util.StringUtil;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 扫描类、包处理器
 */
public class ScanClassHandler {
    static final Log LOG = LogFactory.getLog(ScanClassHandler.class);
    static DefaultSingletonBeanRegistry registry = DefaultSingletonBeanRegistry.me();

    private static ScanClassHandler me = new ScanClassHandler();
    private List<String> packageList = new ArrayList<>();

    private ScanClassHandler() {
    }

    public static ScanClassHandler me() {
        return me;
    }

    /**
     * 添加包路径，供 scan() 扫描用
     *
     * @param packagePath
     */
    public void addPackage(String packagePath) {
        packageList.add(packagePath);
    }

    /**
     * 直接添加类路径
     *
     * @param arr
     */
    public void addClassPath(String[] arr) {
        List<String> list = Arrays.asList(arr);
        addClassPath(list);
    }

    /**
     * 添加类路径
     *
     * @param list
     */
    public void addClassPath(List<String> list) {
        for (String item : list) {
            addClassPath(item);
        }
    }

    /**
     * 添加类路径
     *
     * @param path
     */
    public void addClassPath(String path) {
        if (StringUtil.isEmpty(path)) {
            IllegalArgumentException e = new IllegalArgumentException("path 不能为空！");
            LOG.error("", e);
            return;
        }
        Class<?> clazz = null;
        try {
            clazz = Class.forName(path);
        } catch (ClassNotFoundException e) {
            LOG.error("类 " + path + " 不存在！", e);
        }
        registry.addClass(clazz);

    }

    /**
     * 添加 Class
     *
     * @param clazz
     */
    public void addClass(Class clazz) {
        registry.addClass(clazz);
    }

    /**
     * 添加 Class
     *
     * @param arr
     */
    public void addClass(Class[] arr) {
        for (Class item : arr) {
            registry.addClass(item);
        }
    }

    /**
     * 扫描添加的包路径，这里只处理 类名上有 spring注解的 类
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public void scanAndFilter() throws IOException, ClassNotFoundException {
        for (String item : packageList) {
            String basePackage = item.replaceAll("\\.", "/");
            //获取要加载的包路径
            URL url = Thread.currentThread().getContextClassLoader().getResource("/" + basePackage);
            if (url == null) {
                throw new FileNotFoundException("路径 [ " + basePackage + " ] 不存在！");
            }
            if (url.getProtocol().startsWith("file")) {
                scanPackageAndFilter(item);
            } else if (url.getProtocol().startsWith("jar")) {
                scanJarAndFilter(item);
            }
        }

    }

    /**
     * 扫描需要加载的类路径
     *
     * @param basePackage
     */
    private void scanPackageAndFilter(String basePackage) throws FileNotFoundException, ClassNotFoundException {
        //获取要加载的包路径
        URL resource = this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\\.", "/"));
        if (resource == null) {
            throw new FileNotFoundException("路径 [ " + basePackage + " ] 不存在！");
        }
        String basePath = resource.getFile();
        File baseDir = new File(basePath);
        //递归遍历目录下的class文件，收集要加载的类路径
        File[] files = baseDir.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                scanPackageAndFilter(basePackage + "." + file.getName());
            } else {
                if (file.getName().endsWith(".class")) {
                    String classPath = basePackage + "." + file.getName().replaceAll(".class", "");
//                    classPaths.add(classPath);
                    Class<?> clazz = Class.forName(classPath);
                    if(AnnotationFilter.hasClassAnnotation(clazz)) {
                        registry.addClass(clazz);
                    }
                }
            }
        }
    }

    /**
     * 指定路径，扫描jar包中该路径下的类、及子路径下的类
     * 对类进行过滤
     * @param packagePath
     * @throws IOException
     */
    private void scanJarAndFilter(String packagePath) throws IOException,ClassNotFoundException {
        // 可能不同的jar包下，都有该路径，所以要用 getResources() 方法
        Enumeration<URL> dirs = ClassUtils.getDefaultClassLoader().getResources(packagePath.replace(".", "/"));
        while (dirs.hasMoreElements()) {
            URL url = dirs.nextElement();
            String protocol = url.getProtocol();
            // 只处理 jar包中的
            if ("jar".equals(protocol)) {
                //转换为JarURLConnection
                JarURLConnection connection = (JarURLConnection) url.openConnection();
                if (connection != null) {
                    // 获取包含该包路径的 jar 包
                    JarFile jarFile = connection.getJarFile();
                    if (jarFile != null) {
                        //得到该jar文件下面的文件、目录
                        Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
                        while (jarEntryEnumeration.hasMoreElements()) {
                        /*entry的结果大概是这样：
                                org/
                                org/junit/
                                org/junit/rules/
                                org/junit/runners/
                                org/junit/rules/Main.class
                                org/junit/rules/Test.class
                         */
                            JarEntry entry = jarEntryEnumeration.nextElement();
                            String path = entry.getName();
                            // 将路径中的 / 替换成 .
                            String classPath = path.replaceAll("/", ".");
                            /*
                             获取的是 当前 jar下的所有文件、路径，所以这里要过滤
                             只处理 前缀是指定 包路径的 class文件
                             */
                            if (classPath.startsWith(packagePath) && !entry.isDirectory() ) {

                                if (classPath.endsWith(".class")) {
                                    // 截取类名，不包含 .class
                                    String classAllPath = classPath.substring(0, classPath.lastIndexOf("."));
                                    Class<?> clazz = Class.forName(classAllPath);
                                    if(AnnotationFilter.hasClassAnnotation(clazz)) {
                                        registry.addClass(clazz);
                                    }
                                }

                            }
                        }
                    }
                }
            }
        }
    }

}
